/**
 * Class to simulate a Processor.
 * CS 149 Project, Phase 1
 * @author Timmy Yee
 * @author Keven Wong
 */

public class Processor
{
    private static Process current;
    private static boolean pending;
    private static int timeSlice = 50;
    private static int contextSwitch = 0;
    private static int displayInterval = 100;
    private static int pauseInterval = 500;
    private static double timer = timeSlice;
    private static double switchTime = 0;

    /**
     * Checks for the timer interrupt that is used for round robin.
     * @param time - absolute current time
     * @param gui - the panel containing the trace
     */
    private static void checkTimer(double time, Display gui)
    {
        if (timeSlice == 0 ||
            Scheduler.getAlgorithm() != Algorithm.ROUND_ROBIN)
            return;

        timer -= Simulator.TIME_UNIT;
        if (Util.areDoublesEqual(timer, 0)) {
            gui.appendLog(time, "Timer interrupt occurred\n");
            Scheduler.swapProcesses(time, gui);
            timer = timeSlice;
        }
    }

    /**
     * Updates and appends trace every given time interval.
     * @param time - absolute current time
     * @param gui - the panel containing the trace
     */
    private static void checkQueueDisplay(double time, Display gui)
    {
        if (displayInterval == 0)
            return;

        if (Util.nearestTenth(time) % displayInterval == 0)
            gui.appendLog(time, Scheduler.getQueuesContent());
    }

    /**
     * Checks for disk interrupts.
     * @param time - absolute current time
     * @param gui - the panel containing the trace
     */
    private static void checkDisk(double time, Display gui)
    {
        Process p = Scheduler.getProcessFromDiskQ(time);
        if (p != null) {
            Scheduler.offerToReadyQ(p, time);
            gui.appendLog(time, "Disk interrupt occurred\n");
            gui.appendLog(time, "Disk operation terminated for process " +
                                p.getID() + "\n");
            gui.appendLog(time, "Process " + p.getID() +
                                " moved from Disk Queue to Ready Queue\n");
        }
    }

    /**
     * Checks for print interrupts.
     * @param time - absolute current time
     * @param gui - the panel containing the trace
     */
    private static void checkPrinter(double time, Display gui)
    {
        Process p = Scheduler.getProcessFromPrinterQ(time);
        if (p != null) {
            Scheduler.offerToReadyQ(p, time);
            gui.appendLog(time, "Print interrupt occurred\n");
            gui.appendLog(time, "Print operation terminated for process " +
                                p.getID() + "\n");
            gui.appendLog(time, "Process " + p.getID() +
                                " moved from Printer Queue to Ready Queue\n");
        }
    }

    /**
     * Checks and handles each type of interrupt.
     * @param time - absolute current time
     * @param gui - the panel containing the trace
     */
    public static void handleInterrupts(double time, Display gui)
    {
        // let's not do interrupts right when the simulator starts up
        if (time == 0)
            return;

        // poll each possible type of interrupt
        checkTimer(time, gui);
        checkQueueDisplay(time, gui);
        checkDisk(time, gui);
        checkPrinter(time, gui);
    }

    /**
     * Sets the time slice of processes.
     * @param time - time slice
     */
    public static void setTimeSlice(int time)
    {
        timeSlice = time;
    }

    /**
     * Gets the time slice of processes.
     * @return time slice
     */
    public static int getTimeSlice()
    {
        return timeSlice;
    }

    /**
     * Set the context switch time.
     * @param i - context switch time
     */
    public static void setContextSwitch(int i)
    {
        contextSwitch = i;
    }

    /**
     * Gets the context switch time.
     * @return - context switch time
     */
    public static int getContextSwitch()
    {
        return contextSwitch;
    }

    /**
     * Set the display interval in the trace.
     * @param i - display interval
     */
    public static void setDisplayInterval(int i)
    {
        displayInterval = i;
    }

    /**
     * Gets the display interval in the trace.
     * @return - display interval
     */
    public static int getDisplayInterval()
    {
        return displayInterval;
    }

    /**
     * Set the pause interval in the trace.
     * @param i - pause interval
     */
    public static void setPauseInterval(int i)
    {
        pauseInterval = i;
    }

    /**
     * Gets the pause interval in the trace.
     * @return - pause interval
     */
    public static int getPauseInterval()
    {
        return pauseInterval;
    }

    /**
     * Gets the current process.
     * @return the current process
     */
    public static Process getCurrent()
    {
        return current;
    }

    /**
     * Make the process finally assigned when the context switch is over.
     * @param time - absolute current time
     * @param gui - the panel containing the trace
     */
    private static void finallySwitch(double time, Display gui)
    {
        if (current != null) {
            if (current.hasNotBeenAssignedBefore())
                current.setResponseTime(time);

            gui.appendLog(time, "Process " + current.getID() +
                                " assigned to the CPU\n");
        }
    }

    /**
     * Attempt to assign a process from the ready queue.
     * @param time - absolute current time
     * @param gui - the panel containing the trace
     */
    public static void assignProcess(double time, Display gui)
    {
        timer = timeSlice;
        switchTime = contextSwitch;
        current = Scheduler.pollFromReadyQ(time);

        if (switchTime == 0)
            finallySwitch(time, gui);
    }

    /**
     * Checks if processor is currently in a context switch.
     * @return true if switching to a process, false otherwise
     */
    public static boolean hasOverhead()
    {
        return !(switchTime < Simulator.TIME_UNIT);
    }

    /**
     * Continues the context switch.
     * @param time - absolute current time
     * @param gui - the panel containing the trace
     */
    public static void continueSwitch(double time, Display gui)
    {
        switchTime -= Simulator.TIME_UNIT;

        if (switchTime < Simulator.TIME_UNIT)
            pending = true;
    }

    /**
     * Determines whether there are no processes running
     * @return true if no processes running, false otherwise
     */
    public static boolean hasNoProcess()
    {
        return current == null;
    }

    /**
     * Runs a process.
     * @param time - absolute current time
     * @param gui - the panel containing the trace
     */
    public static void run(double time, Display gui)
    {
        if (hasNoProcess())
            return;

        if (pending) {
            finallySwitch(time, gui);
            pending = false;
        }

        if (current.isDone()) {
            Scheduler.terminateProcess(current, time, gui);
            assignProcess(time, gui);
        }

        if (hasNoProcess())
            return;

        // check for system calls only every msec
        if (!hasOverhead() && Util.isWholeNumber(time))
            if (current.doDiskRequest()) {
                Scheduler.assignToDisk(current, time, gui);
                assignProcess(time, gui);
            } else if (current.doPrintRequest()) {
                Scheduler.assignToPrinter(current, time, gui);
                assignProcess(time, gui);
            }

        if (hasNoProcess())
            return;

        current.doWork(time, gui);
    }
}

